﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jacaranda.Data;
using System.Data;
using System.Reflection;
using System.Collections;
using System.IO;
using Jacaranda.Rules;
using System.Runtime.Serialization;

namespace Jacaranda.Model
{
    [DataContract]
    public class Entity
    {
        public Entity()
        {
            ApplyDefaultFieldValues();
        }

        private void ApplyDefaultFieldValues()
        {
            foreach (FieldMetadata field in Metadata.Type(GetType()).Fields)
            {
                field.ApplyDefaultValue(this);
            }
        }


        public static EntityMetadata[] Register(params Assembly[] assemblies)
        {
            Metadata.ClearMetadata();

            foreach (Assembly assembly in assemblies)
            {
                Metadata.RegisterAssembly(assembly);
            }

            return Metadata.RegisteredTypes.ToArray();
        }


        internal Transaction Transaction { get; set; }

        public void SetTransaction(Transaction transaction)
        {
            Transaction = transaction;
        }


        protected AssociationField GetAssociationField(string propertyName)
        {
            EntityMetadata current = Metadata.Type(GetType());

            while (current != null)
            {
                AssociationField association = current.AssociationFields.FirstOrDefault(i => i.PropertyName == propertyName);

                if (association != null)
                {
                    return association;
                }

                current = current.BaseType;
            }

            return null;
        }

        public FieldInfo GetFieldInfo(string fieldName)
        {
            EntityMetadata current = Metadata.Type(GetType());

            while (current != null)
            {
                FieldInfo field = current.Type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
                
                if (field != null)
                {
                    return field;
                }

                current = current.BaseType;
            }

            return null;
        }



        protected List<string> changedAssociationProperties;

        protected List<string> loadedDependencies;


        protected T TryLoadSingle<T>(ref T entity, string propertyName) where T : Entity
        {
            if (loadedDependencies == null)
            {
                loadedDependencies = new List<string>();
            }

            if (loadedDependencies.Contains(propertyName))
            {
                return entity;
            }

            List<T> entities = Metadata.Type(GetType()).FindAssociationField(propertyName)
                .Load(this, Transaction)
                .Cast<T>()
                .ToList();

            entity = (entities.Count == 0) ? null : (T)entities[0];

            loadedDependencies.Add(propertyName);
            return entity;
        }

        protected EntityList<T> TryLoadList<T>(ref EntityList<T> entities, string propertyName) where T : Entity
        {
            if (changedAssociationProperties == null)
            {
                changedAssociationProperties = new List<string>();
            }

            if (!changedAssociationProperties.Contains(propertyName))
            {
                changedAssociationProperties.Add(propertyName);
            }

            if (entities == null)
            {
                entities = new EntityList<T>() { Owner = this, PropertyName = propertyName };
            }

            return entities;
        }

        public void Delete()
        {
            Transaction tran = Transaction;

            bool isNewTransaction = false;
            if (tran == null)
            {
                tran = DataPortal.BeginTransaction();
                isNewTransaction = true;
            }

            try
            {
                foreach (EntityAssociation association in Metadata.Type(GetType()).Associations)
                {
                    object primaryKeyValue = Metadata.Type(GetType()).PrimaryKey.GetValue(this);
                    DataPortal.Execute(string.Format("DELETE FROM {0} WHERE Key1='{1}' OR Key2='{2}'", association.TableName, primaryKeyValue, primaryKeyValue), tran);
                }

                DataPortal.Execute(string.Format("DELETE FROM {0} WHERE {1}='{2}'", Metadata.Type(GetType()).TableName, Metadata.Type(GetType()).PrimaryKey.FieldName, Metadata.Type(GetType()).GetValueOfPrimaryKey(this)), tran);

                if (isNewTransaction)
                {
                    tran.Commit();
                }
            }
            catch
            {
                if (isNewTransaction)
                {
                    tran.Rollback();
                }

                throw;
            }
        }


        public static void GenerateAbstract(string namespaceName, string fileName)
        {
            StreamWriter file = new StreamWriter(fileName);

            file.WriteLine("using System;");
            file.WriteLine("using Jacaranda;");
            file.WriteLine("using Jacaranda.Model;");
            file.WriteLine();

            file.WriteLine(string.Format("namespace {0}", namespaceName));
            file.WriteLine("{");


            file.Write(GenerateAbstractClass());
            file.WriteLine();


            foreach (Type type in Metadata.RegisteredTypes.Select(i => i.Type))
            {
                file.Write(GenerateAbstractForType(type));
                file.WriteLine();
            }

            file.WriteLine("}");

            file.Close();
        }

        private static string GenerateAbstractClass()
        {
            StringBuilder b = new StringBuilder();

            b.AppendLine("\tpublic static class DB");
            b.AppendLine("\t{");


            foreach (Type type in Metadata.RegisteredTypes.Select(i => i.Type))
            {
                b.AppendLine("\t\t/// <summary>");
                b.AppendFormat("\t\t/// {0}\r\n", Metadata.Type(type).DisplayName);
                b.AppendLine("\t\t/// </summary>");

                b.AppendFormat("\t\tpublic static readonly {0}Abstract {1} = new {2}Abstract();\r\n", type.Name, type.Name, type.Name);
                b.AppendLine();
            }

            b.AppendLine("\t}");

            return b.ToString();
        }

        private static string GenerateAbstractForType(Type type)
        {
            StringBuilder b = new StringBuilder();

            b.AppendFormat("\tpublic class {0}Abstract : Abstract\r\n", type.Name);
            b.AppendLine("\t{");

            foreach (FieldMetadata field in Metadata.Type(type).Fields)
            {
                b.AppendLine("\t\t/// <summary>");
                b.AppendFormat("\t\t/// <para>{0}</para>\r\n", field.DisplayName);
                
                if (field.DefaultValue != null)
                {
                    b.AppendFormat("\t\t/// <para>默认：{0}</para>\r\n", field.DefaultValue);
                }

                if (field.PropertyType.Equals(typeof(string)))
                {
                    b.AppendFormat("\t\t/// <para>长度：{0}</para>\r\n", (field.Length == null) ? "MAX" : field.Length.ToString());
                }

                b.AppendLine("\t\t/// </summary>");

                b.AppendFormat("\t\tpublic readonly AbstractProperty {0} = new AbstractProperty(\"{1}\");\r\n\r\n", field.PropertyName, field.PropertyName);
            }

            b.AppendLine("\t}");

            return b.ToString();
        }


        public override bool Equals(object obj)
        {
            if (obj is Entity && GetType().Equals(obj.GetType()))
            {
                object key1 = Metadata.Type(GetType()).GetValueOfPrimaryKey(this);
                object key2 = Metadata.Type(obj.GetType()).GetValueOfPrimaryKey(obj as Entity);

                if (key1 == null)
                {
                    return false;
                }
                else
                { 
                    return key1.Equals(key2);
                }
            }
            else
            {
                return base.Equals(obj);
            }
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public virtual void AfterLoad()
        {
        }


        public virtual ValidationRuleViolation[] Validate()
        {
            return Validate(null);
        }

        public ValidationRuleViolation[] Validate(string propertyNamespace)
        {
            List<ValidationRuleViolation> results = new List<ValidationRuleViolation>();

            foreach (FieldMetadata field in Metadata.Type(GetType()).Fields.Where(i => i.ValidationRules != null))
            {
                foreach (ValidationRule validationRule in field.ValidationRules)
                {
                    if (!validationRule.Validate(this, field.PropertyName, field.GetValue(this)))
                    {
                        results.Add(new ValidationRuleViolation() 
                        { 
                            PropertyName = propertyNamespace + field.PropertyName,
                            DisplayName = field.DisplayName, 
                            RuleName = validationRule.GetType().Name, 
                            RuleParameters = validationRule.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Select(i => new KeyValuePair<string, object>(i.Name, i.GetValue(validationRule, null))).ToArray()
                        });

                        break;
                    }
                }
            }

            return results.ToArray();
        }
    }
}
